// ----------------------------------------------------------------------------
/**
 * @file    server.cpp
 *
 * @brief   .cpp file for Server
 *
 *
 *
 * @author  Siddharth Sathyam <ssathy2@gmail.com>
 * @author  Alan Ruvalcaba <aruval3@gmail.com>
 * @author  Patrick LaMontagna <plamontagna@gmail.com
 *
 * @version 1.0
 *
 * @date    November 24th, 2011
 *
 */
// ----------------------------------------------------------------------------

#include "server.h"

Server::Server(QObject * parent) :
    QTcpServer(parent)
{
}

bool Server::startServer(QHostAddress addr, quint16 port)
{
    return this->listen(addr, port);
}

void Server::incomingConnection(int handle)
{
    //create a socket with this handle to talk to the client
    QTcpSocket *client = new QTcpSocket(this);
    client->setSocketDescriptor(handle);

    qDebug() << QTime::currentTime().toString(Qt::SystemLocaleDate) << ": Client Connected From - " << client->peerAddress().toString() << ":" << client->peerPort();
    userSocketStringTable.insert(client, "");
    connect(client, SIGNAL(readyRead()), this, SLOT(handleIncomingData()));
    connect(client, SIGNAL(disconnected()), this, SLOT(handleDisconnection()));
}

void Server::satisfyAnyRequests(clientInformation *userToSendTo)
{
    foreach (clientInformation* client, currentFriendRequests.values())
    {
	if(userToSendTo->getUserName() == client->getUserName())
	{
	    connect(this, SIGNAL(addFriend(QString)),
		    client, SLOT(handleAddFriend(QString)));

	    QString personSendingRequest = currentFriendRequests.key(client)->getUserName();
	    emit addFriend(personSendingRequest);

	    disconnect(this, SIGNAL(addFriend(QString)),
		       client, SLOT(handleAddFriend(QString)));
	}
    }
}

void Server::handleIncomingData() {

    // create a pointer to the socket that sent data
    QTcpSocket *socketThatSentData = (QTcpSocket*)sender();

    // check if the sender socket is valid, then only read the data
    if(socketThatSentData->isValid())
    {
	QDataStream recvStream(socketThatSentData);
	recvStream.setVersion(QDataStream::Qt_4_7);
	quint16 packId = 0;
	quint16 blockSize = 0;

	// read in data from the socket in accordance with the protocol that was decided
	if(blockSize == 0)
	{
	    if(socketThatSentData->bytesAvailable() < sizeof(quint16))
	    {
		return;
	    }

	    recvStream >> packId;

	    switch(packId)
	    {
		// if the user sends an establishing data packet, then deal with that separately
		case 0:
		{
		    QString userFromSocket = "";

		    blockSize = sizeof(QString);
		    if(socketThatSentData->bytesAvailable() < blockSize)
		    {
			return;
		    }

		    recvStream >> userFromSocket;

		    // brand new user joined
		    if(!overallUsers.contains(userFromSocket) && !(userSocketStringTable.values().contains(userFromSocket)))
		    {
			qDebug() << QTime::currentTime().toString(Qt::SystemLocaleDate) << ": " << "Client: " << userFromSocket << " at " << socketThatSentData->peerAddress().toString() << "@" << socketThatSentData->peerPort() << endl << flush;
			userSocketStringTable.insert(socketThatSentData, userFromSocket);
			clientInformation* userJustJoined = new clientInformation(userFromSocket, socketThatSentData);
			userStringClientInformationTable.insert(userFromSocket, userJustJoined);
			overallUsers.insert(userFromSocket, userJustJoined);
		    }
		    // user that has logged on before joined
		    else if(overallUsers.contains(userFromSocket) && !(userSocketStringTable.values().contains(userFromSocket)))
		    {
			qDebug() << QTime::currentTime().toString(Qt::SystemLocaleDate) << ": " << "Client: " << userFromSocket << " at " << socketThatSentData->peerAddress().toString() << "@" << socketThatSentData->peerPort() << endl << flush;
			clientInformation* userJustJoined = overallUsers.value(userFromSocket);
			userSocketStringTable.insert(socketThatSentData, userFromSocket);
			userJustJoined->updateSocket(socketThatSentData);
			userStringClientInformationTable.insert(userFromSocket, userJustJoined);

			if(currentFriendRequests.values().contains(userJustJoined))
			{
			    satisfyAnyRequests(userJustJoined);
			}
			else
			{
			    updateUsers();
			}
		    }
		    else if(overallUsers.contains(userFromSocket) && userSocketStringTable.values().contains(userFromSocket))
		    {
			qDebug() << QTime::currentTime().toString(Qt::SystemLocaleDate) << ": " << "Invalid Username" << userFromSocket << " at " << socketThatSentData->peerAddress().toString() << "@" << socketThatSentData->peerPort() << endl << flush;
			userSocketStringTable.remove(socketThatSentData);
		    }

		    qDebug() << "Users Online: " << userSocketStringTable.count() << endl;
		    break;
		}

		// User sent a message so route it to the right person
		case 1:
		{
		    QString messageSentFromUser = "";
		    QString messageSentToUser = "";
		    QString message = "";
		    QString timeSent = "";

		    blockSize = sizeof(QString) + sizeof(QString)+ sizeof(QString) + sizeof(QString);
		    if (socketThatSentData->bytesAvailable() <  blockSize)
		    {
			return;
		    }

		    recvStream >> messageSentFromUser >> messageSentToUser >> message >> timeSent;
		    clientInformation* personToSentTo = userStringClientInformationTable.value(messageSentToUser);
		    if(personToSentTo->sendMessage(messageSentFromUser, message, timeSent))
		    {
			qDebug() << "Message Successfully Sent from " + messageSentFromUser + " to "  + messageSentToUser + "-> " + message << endl;
		    }
		    else
		    {
			qDebug() << "Message Not Successfully Sent from " + messageSentFromUser + " to "  + messageSentToUser << endl;
		    }

		    break;
		}

		// User sent an add friend request
		case 4:
		{
		    qDebug() << "Friend Request Recieved";
		    QString personToAddAsFriend = "";
		    blockSize = sizeof(QString);
		    if(socketThatSentData->bytesAvailable() < blockSize)
		    {
			return;
		    }
		    recvStream >> personToAddAsFriend;
		    if(overallUsers.keys().contains(personToAddAsFriend))
		    {
			QString personSendingRequest = userSocketStringTable.value(socketThatSentData);
			qDebug() << personSendingRequest << " to " << personToAddAsFriend;
			handleIncomingFriendRequest(personSendingRequest, personToAddAsFriend, true);
		    }
		    else
		    {
			QString personSendingRequest = userSocketStringTable.value(socketThatSentData);
			handleIncomingFriendRequest(personSendingRequest, personToAddAsFriend, false);
		    }
		    break;
		}

		// user sent a packet that confirmed or denied the friend request
		case 7:
		{
		    qDebug() << "Friend Request Confirmed";
		    QString recipientUser = "";
		    QString sentRequestUser = "";
		    bool isAccepted = false;
		    blockSize = sizeof(QString) + sizeof(QString) + sizeof(bool);
		    if(socketThatSentData->bytesAvailable() < blockSize)
		    {
			return;
		    }

		    recvStream >> recipientUser >> sentRequestUser >> isAccepted;
		    clientInformation* recipient = overallUsers.value(recipientUser);
		    clientInformation* sentUser = overallUsers.value(sentRequestUser);

		    connect(this, SIGNAL(confirmFriendRequest(clientInformation*,bool)),
			    recipient, SLOT(handleFriendAcceptance(clientInformation*,bool)));

		    emit confirmFriendRequest(sentUser, isAccepted);

		    disconnect(this, SIGNAL(confirmFriendRequest(clientInformation*,bool)),
			    recipient, SLOT(handleFriendAcceptance(clientInformation*,bool)));

		    if(currentFriendRequests.contains(sentUser))
		    {
			currentFriendRequests.remove(sentUser);
		    }
		    updateUsers();
		    break;
		}

		// person wants to remove a friend
		case 8:
		{

		    QString personToRemove = "";
		    blockSize = sizeof(QString);
		    if(socketThatSentData->bytesAvailable() < blockSize)
		    {
			return;
		    }
		    recvStream >> personToRemove;
		    QString personSendingRequest = userSocketStringTable.value(socketThatSentData);
		    qDebug() << "Remove Request Recieved From: " + personSendingRequest + " to " + personToRemove;
		    handleIncomingRemoveFriendRequest(personSendingRequest, personToRemove);
		    updateUsers();
		    break;
		}
		default:
		{
		    qDebug() << "Invalid request";
		    break;
		}
	    }
	}
    }
    else
    {
	qDebug() << QTime::currentTime().toString(Qt::SystemLocaleDate) << ": " << socketThatSentData->errorString() << endl << flush;
    }
}

void Server::handleDisconnection()
{
    // Create a socket that will be a pointer to the socket that sent the signal
    QTcpSocket *disconnectingClient = (QTcpSocket*)sender();

    if(userSocketStringTable.contains(disconnectingClient))
    {
	QString disconnectingUserName = userSocketStringTable.find(disconnectingClient).value();

	//remove socket and username from the tables
	userStringClientInformationTable.remove(disconnectingUserName);
	userSocketStringTable.remove(disconnectingClient);

	updateUsers();

	qDebug() << QTime::currentTime().toString(Qt::SystemLocaleDate) <<  ": Client disconnected - " << disconnectingUserName << "@" << disconnectingClient->peerAddress().toString() << ":" << disconnectingClient->peerPort();
	qDebug() << "Users Currently Online: " << userSocketStringTable.count() << endl;
	qDebug() << "All Overall Users: " << overallUsers.size();
    }
    else
    {
	//@TODO Client Disconnection Problem...Uh oh something went horribly wrong
    }
}

void Server::updateUsers()
{
    // iterate over all of the users online and send them their friends list
    foreach (clientInformation* client, userStringClientInformationTable.values())
    {
	client->sendOnlineFriendsList(userStringClientInformationTable.keys());
    }
}

void Server::handleIncomingFriendRequest(QString personSendingRequest, QString personRecievingRequest, bool isValid)
{
    clientInformation* userSendingRequest = userStringClientInformationTable.value(personSendingRequest);

    connect(this, SIGNAL(sendIsValidFriendRequest(QString,bool)),
	    userSendingRequest, SLOT(handleIsValidFriendRequest(QString,bool)));

    if(isValid)
    {
	// make sure that the request that was sent wasn't sent already...to prevent
	// user from sending multiple requests to one person
	if(!currentFriendRequests.contains(userSendingRequest))
	{
	    //check if recieving user is online
	    if(userStringClientInformationTable.keys().contains(personRecievingRequest) )
	    {
		clientInformation* user = userStringClientInformationTable.value(personRecievingRequest);

		if(!user->isUserFriend(personSendingRequest))
		{
		    currentFriendRequests.insert(userSendingRequest, user);
		    // pass on the request
		    connect(this, SIGNAL(addFriend(QString)),
			    user, SLOT(handleAddFriend(QString)));

		    emit addFriend(personSendingRequest);

		    disconnect(this, SIGNAL(addFriend(QString)),
			    user, SLOT(handleAddFriend(QString)));

		    // send it to the requesting user that the request was sent successfully
		    emit sendIsValidFriendRequest(personRecievingRequest, true);
		}
		// person is already a friend
		else
		{
		    emit sendIsValidFriendRequest(personRecievingRequest, false);
		}
	    }
	    // user is not online
	    else if(!userStringClientInformationTable.keys().contains(personRecievingRequest) )
	    {
		clientInformation* user = overallUsers.value(personRecievingRequest);
		if(!user->isUserFriend(personSendingRequest))
		{
		    // store the request
		    currentFriendRequests.insertMulti(userSendingRequest, user);

		    // send it to the requesting user that the request was sent successfully
		    emit sendIsValidFriendRequest(personRecievingRequest, true);
		}

		// if the user is already a friend tell the person sending that it's an invalid request
		else
		{
		    emit sendIsValidFriendRequest(personRecievingRequest, false);
		}
	    }
	}
	// if the request already exists then tell the sending user this is invalid
	else
	{
	    emit sendIsValidFriendRequest(personRecievingRequest, false);
	}
    }
    // if the request is invalid then tell the user that
    else
    {
	emit sendIsValidFriendRequest(personRecievingRequest, false);
    }

    disconnect(this, SIGNAL(sendIsValidFriendRequest(QString,bool)),
	    userSendingRequest, SLOT(handleIsValidFriendRequest(QString,bool)));
}

void Server::handleIsValidUserName(QTcpSocket *personThatSentData, QString nameOfUser, bool isValidUsername)
{
    QByteArray dataBlock;
    QDataStream sendStream(&dataBlock, QIODevice::WriteOnly);
    sendStream.setVersion(QDataStream::Qt_4_7);

    // Message Header <3> <QString userName added> <bool whether or not username was added or not>
    sendStream << quint16(3) << (QString) nameOfUser << isValidUsername;
    personThatSentData->write(dataBlock);
    personThatSentData->flush();
    personThatSentData->waitForBytesWritten();
}

void Server::handleIncomingRemoveFriendRequest(QString personSendingRequest, QString personToRemove)
{
    clientInformation* userSendingRequest = userStringClientInformationTable.value(personSendingRequest);

    // make sure that the person that needs to be removed exists
    if(overallUsers.keys().contains(personToRemove))
    {
	clientInformation* userToRemove = overallUsers.value(personToRemove);

	connect(this, SIGNAL(removeFriend(clientInformation*,bool)), userToRemove , SLOT(handleRemoveFriend(clientInformation*,bool)));
	emit removeFriend(userSendingRequest, true);
	disconnect(this, SIGNAL(removeFriend(clientInformation*,bool)), userToRemove , SLOT(handleRemoveFriend(clientInformation*,bool)));
    }
    // if the request is invalid then tell the user that
    else
    {
	connect(this, SIGNAL(sendIsValidRemoveFriendRequest(QString,bool)), userSendingRequest, SLOT(handleIsValidRemoveFriend(QString,bool)));
	emit sendIsValidRemoveFriendRequest(personToRemove, false);
	disconnect(this, SIGNAL(sendIsValidRemoveFriendRequest(QString,bool)), userSendingRequest, SLOT(handleIsValidRemoveFriend(QString,bool)));
    }
}
